﻿using Contrib.Voting.Models;
using NineSeas.Extensions;
using NineSeas.Models;
using Orchard.Data;
using Orchard.Logging;
using Orchard.Services;
using Orchard.Tasks.Scheduling;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace NineSeas.Handlers
{
    /// <summary>
    /// The task will run every monday at 0h
    /// </summary>
    public class WeekCalculateTaskHandler : IWeekCalculateTaskHandler
    {
        private static readonly string _taskType = "WeekCalculateTask";

        private readonly IScheduledTaskManager _taskManager;
        private readonly IClock _clock;
        private readonly IRepository<ResultRecord> _resultRepository;
        private readonly IRepository<VoteRecord> _voteRepository;

        private readonly IRepository<AuthorProfilePartRecord> _authorRepository;
        private readonly IRepository<BookPartRecord> _bookRepository;
        private readonly IRepository<NineSeasSettingsPartRecord> _settingsRepository;
        public ILogger Logger { get; set; }

        public WeekCalculateTaskHandler(IScheduledTaskManager taskManager,
                IRepository<ResultRecord> resultRepository,
                IRepository<VoteRecord> voteRepository,
                IRepository<AuthorProfilePartRecord> authorRepository,
                IRepository<BookPartRecord> bookRepository,
                IRepository<NineSeasSettingsPartRecord> settingsRepository,
                IClock clock)
        {
            this._taskManager = taskManager;
            this._clock = clock;
            this._resultRepository = resultRepository;
            this._voteRepository = voteRepository;
            this._authorRepository = authorRepository;
            this._bookRepository = bookRepository;
            this._settingsRepository = settingsRepository;
            Logger = NullLogger.Instance;

            this._taskManager.CreateTask(_taskType, DateTime.Now.AddMilliseconds(20), null);
        }

        public void Process(ScheduledTaskContext context)
        {
            if (context.Task.TaskType == _taskType)
            {
                try
                {
                    DoWork();
                }
                catch (Exception e)
                {
                    this.Logger.Error(e, e.Message);
                }
                finally
                {
                    ScheduleNextTask(GetNextTaskDate(DateTime.Today));
                }
            }
        }

        private void ScheduleNextTask(DateTime date)
        {
            if (date > DateTime.UtcNow)
            {
                var tasks = this._taskManager.GetTasks(_taskType);
                if (tasks == null || tasks.Count() == 0)
                    this._taskManager.CreateTask(_taskType, date, null);
            }
        }

        public void DoWork()
        {
            // only run if the last run is too far (> 1 day)
            var tasks = this._taskManager.GetTasks(_taskType);
            if (tasks != null && tasks.Any())
            {
                var last = tasks.OrderByDescending(t => t.ScheduledUtc).FirstOrDefault();

                if (last.ScheduledUtc.HasValue && Math.Abs(DateTime.Today.Date.Subtract(last.ScheduledUtc.Value.Date).TotalDays) < 7)
                {
                    return;
                }
            }

            var toDate = DateTime.Today;
            if (toDate.DayOfWeek != DayOfWeek.Monday)
            {
                // get the last monday
                var daysToSubtract = 7 - ((int)DayOfWeek.Monday - (int)toDate.DayOfWeek + 7) % 7;
                toDate = toDate.AddDays(-daysToSubtract);
            }

            var fromDate = toDate.AddDays(-7);

            CalculateAuthorWeek(fromDate, toDate);
            CalculateBookWeek(fromDate, toDate);
            UpdateHomePage();
        }

        private DateTime GetNextTaskDate(DateTime start)
        {
            var actualStart = start.AddDays(1);
            // the next start will be the next monday
            var daysUntilMonday = ((int)DayOfWeek.Monday - (int)actualStart.DayOfWeek + 7) % 7;
            return actualStart.AddDays(daysUntilMonday);
        }

        private void CalculateAuthorWeek(DateTime fromDate, DateTime toDate)
        {
            try
            {
                var authors = _authorRepository.Fetch(a => true);

                foreach (var author in authors)
                {
                    author.WeekLikes = _voteRepository.Count(v => v.ContentItemRecord.Id == author.Id
                        && v.Dimension == Constants.VotingDimensions.AuthorLike
                        && v.Value == 1
                        && v.CreatedUtc > fromDate
                        && v.CreatedUtc <= toDate);

                    author.BookCountWeek = _bookRepository.Count(b => b.AuthorId == author.Id
                        && b.CreatedDate > fromDate
                        && b.CreatedDate <= toDate);

                    _authorRepository.Update(author);
                }

                _authorRepository.Flush();
            }
            catch (Exception e)
            {
                this.Logger.Error(e, e.Message);
            }
        }

        private void CalculateBookWeek(DateTime fromDate, DateTime toDate)
        {
            try
            {
                var books = _bookRepository.Fetch(a => true);

                foreach (var book in books)
                {
                    book.WeekLikes = _voteRepository.Count(v => v.ContentItemRecord.Id == book.Id
                        && v.Dimension == Constants.VotingDimensions.BookLike
                        && v.Value == 1
                        && v.CreatedUtc > fromDate
                        && v.CreatedUtc <= toDate);

                    book.WeekViews = _voteRepository.Count(v => v.ContentItemRecord.Id == book.Id
                        && v.Dimension == Constants.VotingDimensions.BookView
                        && v.Value == 1
                        && v.CreatedUtc > fromDate
                        && v.CreatedUtc <= toDate);

                    _bookRepository.Update(book);
                }

                _bookRepository.Flush();
            }
            catch (Exception e)
            {
                this.Logger.Error(e, e.Message);
            }
        }

        private void UpdateHomePage()
        {
            try
            {
                var settings = _settingsRepository.Fetch(s => true).FirstOrDefault();
                if (settings == null) return;

                settings.BookFavourite = GetBookId(b => b.TotalLikes);
                settings.BookFavouriteWeek = GetBookId(b => b.WeekLikes); 
                settings.BookMostViewed = GetBookId(b => b.TotalViews); 
                settings.BookMostViewedWeek = GetBookId(b => b.WeekViews); 

                settings.AuthorFavouriteWeek = _authorRepository.Fetch(a => true).OrderByDescending(a => a.WeekLikes).FirstOrDefault().Id;
                settings.AuthorNewBookWeek = _authorRepository.Fetch(a => true).OrderByDescending(b => b.BookCountWeek).FirstOrDefault().Id;
            }
            catch (Exception e)
            {
                this.Logger.Error(e, e.Message);
            }
        }

        private int GetBookId(Func<BookPartRecord, int> selector)
        {
            var record = _bookRepository.Fetch(b => true).OrderByDescending(selector).FirstOrDefault();
            return record != null && record.ContentItemRecord != null ? record.ContentItemRecord.Id : 0;
        }

        
        private int GetAuthorId(Func<AuthorProfilePartRecord, int> selector)
        {
            var record = _authorRepository.Fetch(b => true).OrderByDescending(selector).FirstOrDefault();
            return record != null ? record.Id : 0;
        }
    }
}